'''
Stock Item Master:

It is a Stock Code master to record the structure between child and parent,
and other default elements.
E.g.: eDoc, Task, Regular Replacement

QR Code is not for Stock Code Master.
'''
from typing import Tuple

from django.db import IntegrityError, transaction
from django.db.models.query import QuerySet
from backend.cews.models import Parts
from datetime import datetime

from django.db.models import Q
from backend.cews.models import (Asset, EquipmentLevelRelation, StockItem,PartsEDoc,
                                 StockItemRelation, StockItemReplacement,VersionMaster,
                                 StockItemTask, System, Workgroup, PartsReplacement,RepairOrderPart,
                                 StockItemEDoc, RepairOrderPartReplacement)
from backend.cews.models import Log
import backend.cews.services.LogService as LogService
import backend.cews.services.RepairService as RepairService

import json

def save_stock_item(
    stock_code: str,
    car: str,
    workgroup: str,
    system: str,
    short_description: str,
    description: str,
    date_code: str,
    sw_ver: str,
    hw_ver: str,
    obsolescence: int,
    equipment_class: str,
    lv1: str,
    lv2: str,
    lv3: str,
    lv4: str,
    lv5: str = "",
    asset_id: str = "",
    request:str="",
    global_regular_cycle_month: int = 36,
    # parent_stock_item_stock_code: str = ""
) -> Tuple["StockItem | None", "StockItem | None"]:
    '''
    If the stock code does not exist on Master,
    save a copy to stock code master as an initial master data.

    Otherwise, save the dataset itself.
    '''

    stock_item: "StockItem | None" = None
    parent_stock_item: "StockItem | None" = None

    with transaction.atomic():
        if not check_stock_item_exist(stock_code):
            stock_item = StockItem(
                stock_code=stock_code,
                car=car,
                workgroup=workgroup,
                system=system,
                description=short_description,
                detail_description=description,
                date_code=date_code,
                sw_ver=sw_ver,
                hw_ver=hw_ver,
                obsolescence=obsolescence,
                asset_id=asset_id,
                equipment_class=equipment_class,
                global_regular_cycle_month=global_regular_cycle_month,
                lv1=lv1,
                lv2=lv2,
                lv3=lv3,
                lv4=lv4,
                lv5=lv5
            )

            if stock_item.is_parent:
                parent_stock_code = stock_code
                stock_code = ''
            else:
                parent_stock_code = ''
                stock_code = stock_code

            new_log = Log(
                    username=request.user.username,
                    log_type=Log.LogType.STOCKMASTER,
                    category_type=Log.CategoryType.STOCK,
                    action_type=Log.ActionType.CREATE,
                    action='create_stock_item',
                    payload= json.dumps(request.data),
                    description=f"Stock code: {stock_item.stock_code}, description: {short_description} is created",
                    repair_order=0,
                    parent_stock_code=parent_stock_code,
                    parent_part='',
                    part='',
                    stock_code=stock_code
                )


            stock_item.save()
            new_log.save()
        else:
            # stock_item = update_stock_item(
            #     get_stock_item(stock_code),
            #     car,
            #     workgroup,
            #     system,
            #     short_description,
            #     description,
            #     date_code,
            #     sw_ver,
            #     hw_ver,
            #     asset_id,
            #     equipment_class,
            #     lv1,
            #     lv2,
            #     lv3,
            #     lv4,
            #     lv5
            # )
            
            raise ValueError("Duplicated stock code.")

        # if lv5 != "" and parent_stock_item_stock_code != "":
        #     if check_stock_item_exist(parent_stock_item_stock_code):
        #         parent_stock_item = get_stock_item(
        #             parent_stock_item_stock_code
        #         )

        #         stock_item.parent_stock_item = parent_stock_item
        #         stock_item.save()

    return stock_item, parent_stock_item


def update_stock_item(
    current_stock_item: StockItem,
    car: str,
    workgroup: str,
    system: str,
    short_description: str,
    description: str,
    date_code: str,
    sw_ver: str,
    hw_ver: str,
    obsolescence: int,
    asset_id: str,
    equipment_class: str,
    lv1: str,
    lv2: str,
    lv3: str,
    lv4: str,
    lv5: str,
    request:str=""
) -> StockItem:
    with transaction.atomic():
        current_stock_item.car = car
        current_stock_item.workgroup = workgroup
        current_stock_item.system = system
        current_stock_item.description = short_description
        current_stock_item.detail_description = description
        current_stock_item.date_code = date_code
        current_stock_item.sw_ver = sw_ver
        current_stock_item.hw_ver = hw_ver
        current_stock_item.obsolescence = obsolescence
        current_stock_item.asset_id = asset_id
        current_stock_item.equipment_class = equipment_class
        current_stock_item.lv1 = lv1
        current_stock_item.lv2 = lv2
        current_stock_item.lv3 = lv3
        current_stock_item.lv4 = lv4
        current_stock_item.lv5 = lv5
        current_stock_item.save()

        if current_stock_item.is_parent:
            parent_stock_code = current_stock_item.stock_code
            stock_code = ''
        else:
            parent_stock_code = ''
            stock_code = current_stock_item.stock_code

        new_log = Log(
            username=request.user.username,
            log_type=Log.LogType.STOCKMASTER,
            category_type=Log.CategoryType.STOCK,
            action_type=Log.ActionType.UPDATE,
            action='update_stock_item',
            payload= json.dumps(request.data),
            description=f"Stock code: {current_stock_item.stock_code}, description: {short_description} is updated",
            repair_order=0,
            parent_stock_code=parent_stock_code,
            parent_part='',
            part='',
            stock_code=stock_code
        )
        new_log.save()

    return current_stock_item


def get_stock_item(stock_code) -> StockItem:
    '''
    Stock Code as Unique key and cannot change after created.
    '''
    return StockItem.objects.get(
        stock_code=stock_code,
        deleted=False
    )


def check_stock_item_exist(stock_code) -> bool:
    '''
    Stock Code as Unique key and cannot change after created.
    '''

    return StockItem.objects.filter(
        stock_code=stock_code,
        deleted=False
    ).exists()


def get_child_stock_item(
    parent_stock_item_stock_code: str, sort_desc=False
) -> QuerySet[StockItemRelation]:
    sorting_key = "sequence"
    if sort_desc:
        sorting_key = "-" + sorting_key

    return StockItemRelation.objects.filter(
        parent__stock_code=parent_stock_item_stock_code
    ).order_by(sorting_key)


def get_stock_item_task(stock_code: str):
    '''
    A list of Task with name and sort in alphabetic order.
    '''
    return StockItemTask.objects.filter(
        stock_item__stock_code=stock_code,
        deleted=False
    ).order_by("name")


def get_hard_time_replacement(stock_code):
    '''
    Tab – Hard Time replacement
    Listing a list of replacement tasks to do in a certain period.
    Each item has its own replacement tasks.

    Listing
    List of replacement task name and period (month).
    E.g., Relay Replacement, Capacitor Replacement...
    Able to page indexing, header sorting and text filtering.
    '''
    stock_codes = []
    stock_item = get_stock_item(stock_code)
    stock_codes.append(stock_item.stock_code)
    if stock_item.is_parent:
        stock_item_children_relation = stock_item.children
        for stock_item_child_relation in stock_item_children_relation:
            stock_codes.append(stock_item_child_relation.child.stock_code)

    print(stock_codes)
    return StockItemReplacement.objects.filter(
        stock_item__stock_code__in=stock_codes,
        deleted=False
    )


def delete_replacement_task(stock_code):
    # Delete: Delete Replacement Task, restrict if using
    pass


def update_replacement_task_period(id, period, component, request):
    # Edit: Update the period, task name cannot be edited.
    with transaction.atomic():

        current_replacement_task = StockItemReplacement.objects.get(
            id=id,
            deleted=False
        )
        s = StockItem.objects.get(pk=current_replacement_task.stock_item.id)

        update_not_complete_order_part_replacement_task(current_replacement_task, s, period, component)
        
        ori_component = current_replacement_task.component
        # Update parts replacement's period
        current_part_replacement_tasks_update = []
        current_part_replacement_tasks = PartsReplacement.objects.filter(
            parts__stock_code=current_replacement_task.stock_item.stock_code,
            component=ori_component,
            deleted=False
        )
        for current_part_replacement_task in current_part_replacement_tasks:
            current_part_replacement_task.period = period
            current_part_replacement_task.component = component
            current_part_replacement_tasks_update.append(
                current_part_replacement_task
            )
        current_replacement_task.period = period
        current_replacement_task.component = component
        PartsReplacement.objects.bulk_update(current_part_replacement_tasks_update, ("period","component",))

        current_replacement_task.save()

        payload = {
                "stock_code": s.stock_code,
                "component": current_replacement_task.component,
            }
        
        if s.is_parent:
            parent_stock_code = s.stock_code
            stock_code = ''
        else:
            parent_stock_code = ''
            stock_code = s.stock_code

        stock_log = Log(
            username=request.user.username,
            log_type=Log.LogType.STOCKMASTER,
            category_type=Log.CategoryType.HARDTIME,
            action_type=Log.ActionType.UPDATE,
            action='update_replacement_task_period',
            payload= json.dumps(payload),
            description=f"Stock code: {s.stock_code}, component: {ori_component} is updated to {period} months, {component}",
            repair_order=0,
            parent_stock_code=parent_stock_code,
            parent_part='',
            part='',
            stock_code=stock_code
        )
        stock_log.save()

        # for current_part_replacement_task in current_part_replacement_tasks: 
        #     p = Parts.objects.get(pk=current_part_replacement_task.parts.id)
        #     payload = {
        #         "stock_code": p.stock_code,
        #         "serial_no": p.serial_no,
        #         "component": current_part_replacement_task.component,
        #     }
        #     part_log = Log(
        #         username=request.user.username,
        #         log_type=Log.LogType.PARTMASTER,
        #         category_type=Log.CategoryType.HARDTIME,
        #         action_type=Log.ActionType.UPDATE,
        #         action='update_replacement_task_period',
        #         payload= json.dumps(request.data),
        #         description=f"Stock code: {p.stock_code}, serial no: {p.serial_no}, component: {current_part_replacement_task.component}'s period is updated to {period} months",
        #         repair_order=0,
        #         parent_stock_code='',
        #         parent_part='',
        #         part='',
        #         stock_code=s.stock_code
        #         )
        #     part_log.save()

        return True

    return False


def delete_part_replacement(id,request):
    # Edit: Update the period, task name cannot be edited.
    print(id)
    current_replacement_task = StockItemReplacement.objects.get(id=id)


    current_replacement_task = StockItemReplacement.objects.get(
        id=id,
        deleted=False
    )

    # Update parts replacement's period
    current_part_replacement_tasks_update = []
    current_part_replacement_tasks = PartsReplacement.objects.filter(
        parts__stock_code=current_replacement_task.stock_item.stock_code,
        component=current_replacement_task.component,
        deleted=False
    )
    print(current_part_replacement_tasks)

    with transaction.atomic():
        for current_part_replacement_task in current_part_replacement_tasks:
            current_part_replacement_task.delete()
            p = Parts.objects.get(pk=current_part_replacement_task.parts.id)
            payload = {
                "stock_code": p.stock_code,
                "serial_no": p.serial_no,
                "component": current_part_replacement_task.component,
            }
            
            result = LogService.check_part_is_parent(p)

            log = Log(
                username=request.user.username,
                log_type=Log.LogType.PARTMASTER,
                category_type=Log.CategoryType.HARDTIME,
                action_type=Log.ActionType.DELETE,
                action='delete_part_replacement',
                payload= json.dumps(payload),
                description=f"Stock code: {p.stock_code}, serial no: {p.serial_no}, component: {current_part_replacement_task.component} is deleted",
                repair_order=0,
                parent_stock_code=result['parent_stock_code'],
                parent_part=result['parent_serial_no'],
                part=result['serial_no'],
                stock_code=result['stock_code']
            )
            log.save()

        s = StockItem.objects.get(pk=current_replacement_task.stock_item.id)
        roprs = RepairOrderPartReplacement.objects.filter(component=current_replacement_task.component,period=current_replacement_task.period ,repair_order_part__status='CHECKED IN', repair_order_part__stock_code=s.stock_code).exclude(period=0)
        
        for ropr in roprs:
            ropr.delete()
            
        current_replacement_task.delete()

        payload = {
            "stock_code": s.stock_code,
            "component": current_replacement_task.component,
            }
        
        if s.is_parent:
            parent_stock_code = s.stock_code
            stock_code = ''
        else:
            parent_stock_code = ''
            stock_code = s.stock_code

        stock_log = Log(
            username=request.user.username,
            log_type=Log.LogType.STOCKMASTER,
            category_type=Log.CategoryType.HARDTIME,
            action_type=Log.ActionType.DELETE,
            action='delete_stock_replacement',
            payload= json.dumps(payload),
            description=f"Stock code: {s.stock_code}, component: {current_replacement_task.component} is deleted",
            repair_order=0,
            parent_stock_code=parent_stock_code,
            parent_part='',
            part='',
            stock_code=stock_code
        )
        stock_log.save()
        return True

    return False


def create_replacement_task(
    stock_code: str,
    component: str,
    period: int,
    request:str
):
    # Page
    # Add: Insert new replacement task and period.
    new_replacement_task = StockItemReplacement(
        stock_item=get_stock_item(stock_code),
        component=component,
        period=period
    )
    try:
        with transaction.atomic():
            new_replacement_task.save()
            s = StockItem.objects.get(pk=new_replacement_task.stock_item.id)
            if s.is_parent:
                parent_stock_code = s.stock_code
                stock_code = ''
            else:
                parent_stock_code = ''
                stock_code = s.stock_code
            stock_log = Log(
                username=request.user.username,
                log_type=Log.LogType.STOCKMASTER,
                category_type=Log.CategoryType.HARDTIME,
                action_type=Log.ActionType.CREATE,
                action='update_replacement_task_period',
                payload= json.dumps(request.data),
                description=f"Stock code: {s.stock_code}, component: {new_replacement_task.component}'s period is updated to {period} months",
                repair_order=0,
                parent_stock_code=parent_stock_code,
                parent_part='',
                part='',
                stock_code=stock_code
            )
            stock_log.save()
            create_replacement_task_for_related_parts(new_replacement_task, request)
            update_not_complete_order_part_replacement_task(new_replacement_task,s,period,component)
            # return new_replacement_task
    ## handle duplacite error
    except IntegrityError:
        raise ValueError('Duplicate replacement task')
    
    return new_replacement_task

def create_replacement_task_for_related_parts(instance:StockItemReplacement, request):
    
    # create parts replacement if not exisit
    stock_code = instance.stock_item.stock_code
    
    parts_list = Parts.objects.filter(stock_code=stock_code)
    
    parts_replacements = PartsReplacement.objects.filter(parts__in=parts_list,component=instance.component)
    
    parts_replacements_dict = {
        pr.parts_id:pr for pr in parts_replacements
    }
    ## check which part does has parts replacement
    
    parts_no_replacement = []
    
    for parts in parts_list:
        replacement = parts_replacements_dict.get(parts.id,None)
        if replacement is None:
            parts_no_replacement.append(parts)            
            
    repalcement_to_create = []
    for parts in parts_no_replacement:
        parts: Parts
        pr = PartsReplacement()
        pr.parts = parts
        pr.component = instance.component
        pr.period = instance.period
        pr.deleted = instance.deleted
        pr.last_replaced = None

        # part_log = Log(
        #     username=request.user.username,
        #     log_type=Log.LogType.PARTMASTER,
        #     category_type=Log.CategoryType.HARDTIME,
        #     action_type=Log.ActionType.CREATE,
        #     action='create_replacement_task',
        #     payload= json.dumps(request.data),
        #     description=f"Stock code: {parts.stock_code},serial no: {parts.serial_no}, component: {pr.component}'s period is updated to {pr.period} months",
        #     repair_order=0,
        #     parent_stock_code='',
        #     parent_part='',
        #     part=parts.serial_no,
        #     stock_code=parts.stock_code
        # )
        # part_log.save()
        
        try:
            if parts.date_code != "":
                pr.last_replaced = \
                    datetime.strptime(parts.date_code, "%Y/%m")
        except Exception as ex:
            print(ex)
            print(f"Exception during after stockitem replacement created,date code {parts.date_code} is not valid")
            
        repalcement_to_create.append(pr)
    
    PartsReplacement.objects.bulk_create(repalcement_to_create)
    
    

def get_all_lv5_stock_code():
    return StockItem.objects.filter(
        deleted=False
    ).exclude(
        lv5__isnull=True
    ).exclude(
        lv5__exact=''
    )


def remove_stock_item_child(stock_code, parent_stock_code, sequence, request):
    if check_stock_item_exist(stock_code) \
            and check_stock_item_exist(parent_stock_code):
        
        parents =  Parts.objects.filter(stock_code=parent_stock_code).distinct()

        for p in parents:
            if Parts.objects.filter(parent_part=p).exists():
                raise ValueError('Cannot remove when part childs created')

        sir = StockItemRelation.objects.get(
            parent__stock_code=parent_stock_code,
            child__stock_code=stock_code,
            sequence=sequence
        )

        with transaction.atomic():

            sir.delete()

            update_sir_list = StockItemRelation.objects.filter(
                parent__stock_code=parent_stock_code
                ).order_by('sequence')
            
            for index, update_sir in enumerate(update_sir_list):
                update_sir.sequence = index
                update_sir.save()
            
            new_log = Log(
                username=request.user.username,
                log_type=Log.LogType.STOCKMASTER,
                category_type=Log.CategoryType.STOCK,
                action_type=Log.ActionType.DEFITMENT,
                action='remove_stock_item_child',
                payload= json.dumps(request.data),
                description=f"Stock Code:{stock_code} is removed from parent stock code: {parent_stock_code}",
                repair_order=0,
                parent_stock_code=parent_stock_code,
                parent_part='',
                part='',
                stock_code=stock_code
            )

            new_log.save()

            return True

    return False


def assign_stock_item_child(stock_code, parent_stock_code, request):
    if check_stock_item_exist(stock_code) \
            and check_stock_item_exist(parent_stock_code):

        child_stock_item = get_stock_item(stock_code)
        # if len(StockItemRelation.objects.filter(
        #     child=child_stock_item
        # )) > 0:
        #     return None

        parent_stock_item = get_stock_item(
            parent_stock_code
        )

        #if StockItemRelation.objects.filter(
        #    parent=parent_stock_item,
        #    child=child_stock_item
        #).exists():
        #    raise ValueError("Child relation already exists")

        parents =  Parts.objects.filter(stock_code=parent_stock_code).distinct()

        for p in parents:
            if Parts.objects.filter(parent_part=p).exists():
                raise ValueError('Cannot assign when part childs created')

        sir = StockItemRelation(
            parent=parent_stock_item,
            child=child_stock_item,
            sequence=len(
                StockItemRelation.objects.filter(
                    parent=parent_stock_item
                )
            )
        )

        update_sir_list = StockItemRelation.objects.filter(
                parent=parent_stock_item
                ).order_by('sequence')
            
        for index, update_sir in enumerate(update_sir_list):
            update_sir.sequence = index
            update_sir.save()

        new_log = Log(
            username=request.user.username,
            log_type=Log.LogType.STOCKMASTER,
            category_type=Log.CategoryType.STOCK,
            action_type=Log.ActionType.FITMENT,
            action='assign_stock_item_child',
            payload= json.dumps(request.data),
            description=f"Stock Code:{child_stock_item.stock_code} is assigned to parent stock code: {parent_stock_item.stock_code}",
            repair_order=0,
            parent_stock_code=parent_stock_item.stock_code,
            parent_part='',
            part='',
            stock_code=child_stock_item.stock_code
        )

        sir.save()
        new_log.save()
        # child_stock_item.parent_stock_item = parent_stock_item
        # child_stock_item.save()
        return child_stock_item

    return None


def reorder_stock_item_child(parent_stock_code, sequence_dict, request):
    '''
    {
        origin_seq: new_seq
    }
    '''
    """
        version : cannot reorder when part exists
    """

    parents =  Parts.objects.filter(stock_code=parent_stock_code).distinct()

    for p in parents:
        if Parts.objects.filter(parent_part=p).exists():
            raise ValueError('Cannot reorder when part childs created')


    new_sequence = []
    sir = StockItemRelation.objects.filter(
        parent__stock_code=parent_stock_code
    )

    if len(sir) == 0:
        return False

    description = ""

    for (origin_seq, new_seq) in sequence_dict.items():
        relation = sir.get(sequence=origin_seq)
        if origin_seq != new_seq:
            s = StockItem.objects.get(pk=relation.child.id)
            description += f"Stock Code: {s.stock_code} old sequence: {origin_seq}, reorder to: {new_seq}. "
        relation.sequence = new_seq
        new_sequence.append(relation)

    with transaction.atomic():
        StockItemRelation.objects.bulk_update(new_sequence, ("sequence",))
        new_log = Log(
            username=request.user.username,
            log_type=Log.LogType.STOCKMASTER,
            category_type=Log.CategoryType.STOCK,
            action_type=Log.ActionType.UPDATE,
            action='reorder_stock_item_child',
            payload= json.dumps(request.data),
            description=description,
            repair_order=0,
            parent_stock_code=parent_stock_code,
            parent_part='',
            part='',
            stock_code=''
        )
        new_log.save()


        return True

    return False


def get_level_options():
    return Asset.objects.all()


def get_system_options():
    return System.objects.all()


def get_workgroup_options():
    return Workgroup.objects.all()


def get_equipment_level(equipment_class):
    return EquipmentLevelRelation.objects.filter(
        equipment__equipment_class=equipment_class
    )


def delete_task(task_id):
    current_task = StockItemTask.objects.get(pk=task_id)
    # current_task.deleted = True
    current_task.delete()

    # with transaction.atomic():
    #     current_task.save()
    #     return True

    return False

def soft_delete_stock_item(id, user):

    if not user.has_perm('cews.delete_stockitem'):
        raise ValueError('User has no permission to delete stock item.')
    
    s = StockItem.objects.get(pk=id)

    if StockItemRelation.objects.filter(Q(parent=s) | Q(child=s)).exists():
        raise ValueError('Stock Item has relation. Cannot delete')
    
    p = Parts.objects.filter(stock_code=s.stock_code, deleted=False).exists()

    if p:
        raise ValueError('Stock Code is used by parts.')

    payload = {
        "stock_code" : s.stock_code
    }
    if s.is_parent:
        parent_stock_code = s.stock_code
        stock_code = ''
    else:
        parent_stock_code = ''
        stock_code = s.stock_code

    if StockItemReplacement.objects.filter(stock_item=s,deleted=False).exists():
        srl = StockItemReplacement.objects.filter(stock_item=s)
        for sr in srl:
            sr.delete()
            # sr.deleted = True
            # sr.save()
            # new_log = Log(
            #     username=user.username,
            #     log_type=Log.LogType.STOCKMASTER,
            #     category_type=Log.CategoryType.HARDTIME,
            #     action_type=Log.ActionType.DELETE,
            #     action='soft_delete_stock_item',
            #     payload= json.dumps(payload),
            #     description=f"Component: {sr.component} is deleted",
            #     repair_order=0,
            #     parent_stock_code=parent_stock_code,
            #     parent_part='',
            #     part='',
            #     stock_code=stock_code
            # )
            # new_log.save()


    
    if StockItemTask.objects.filter(stock_item=s,deleted=False).exists():
        stl = StockItemTask.objects.filter(stock_item=s,deleted=False)
        for st in stl:
            # st.deleted = True
            # st.save()
            st.delete()
            # new_log = Log(
            #     username=user.username,
            #     log_type=Log.LogType.STOCKMASTER,
            #     category_type=Log.CategoryType.TASK,
            #     action_type=Log.ActionType.DELETE,
            #     action='soft_delete_stock_item',
            #     payload= json.dumps(payload),
            #     description=f"Task: {st.name} is deleted",
            #     repair_order=0,
            #     parent_stock_code=parent_stock_code,
            #     parent_part='',
            #     part='',
            #     stock_code=stock_code
            # )
            # new_log.save()

    if StockItemEDoc.objects.filter(stock_item=s,deleted=False).exists():
        sel = StockItemEDoc.objects.filter(stock_item=s,deleted=False)
        for se in sel:
            # se.deleted = True
            # se.save()
            se.delete()
            # new_log = Log(
            #     username=user.username,
            #     log_type=Log.LogType.STOCKMASTER,
            #     category_type=Log.CategoryType.EDOC,
            #     action_type=Log.ActionType.DELETE,
            #     action='soft_delete_stock_item',
            #     payload= json.dumps(payload),
            #     description=f"EDOC: {se.doc_name} is deleted",
            #     repair_order=0,
            #     parent_stock_code=parent_stock_code,
            #     parent_part='',
            #     part='',
            #     stock_code=stock_code
            # )
            # new_log.save()

    # s.deleted = True
    s.delete()

    # with transaction.atomic():
    new_log = Log(
        username=user.username,
        log_type=Log.LogType.STOCKMASTER,
        category_type=Log.CategoryType.STOCK,
        action_type=Log.ActionType.DELETE,
        action='soft_delete_stock_item',
        payload= json.dumps(payload),
        description=f"Stock item: {s.stock_code} is deleted",
        repair_order=0,
        parent_stock_code=parent_stock_code,
        parent_part='',
        part='',
        stock_code=stock_code
    )
    new_log.save()
    # s.save()
    return True
    

def update_not_complete_order_part_replacement_task(current_replacement_task, stock, new_period, new_component):
    roprs = RepairOrderPartReplacement.objects.filter(component=current_replacement_task.component,repair_order_part__status='CHECKED IN', repair_order_part__stock_code=stock.stock_code).exclude(period=0)
    if roprs.exists():
        for ropr in roprs:
        
            if ropr.period != new_period and ropr.component == new_component:
                ropr.period = new_period
                ropr.save()

            else:
                update_ropr = RepairOrderPartReplacement()
                update_ropr.period = new_period
                update_ropr.repair_order = ropr.repair_order
                update_ropr.repair_order_part = ropr.repair_order_part    
                update_ropr.component = new_component
                update_ropr.last_replaced = ropr.last_replaced
                update_ropr.replaced = None
                update_ropr.save()

                ropr.delete()
              
    else:
        
        rops = RepairOrderPart.objects.filter(status='CHECKED IN', stock_code=stock.stock_code)

        for rop in rops:
            new_repair_order_part_replacement = RepairOrderPartReplacement()
            new_repair_order_part_replacement.period = new_period
            new_repair_order_part_replacement.repair_order = rop.repair_order
            new_repair_order_part_replacement.repair_order_part = rop    
            new_repair_order_part_replacement.component = \
                new_component

            new_repair_order_part_replacement.last_replaced = None
            new_repair_order_part_replacement.replaced = None
            new_repair_order_part_replacement.save()

def edoc_completion_rate(stock_item):
    def check_parts_edoc(parts:Parts):
                
        ## stock_code
        stock_code = parts.stock_code
        
        ## get parts edoc
        parts_edocs = PartsEDoc.objects.filter(parts=parts)
        parts_edocs_dict = {p.stock_code+p.doc_name+p.version:p for p in parts_edocs}
        
        ## get stock item edoc
        stock_item_edocs = StockItemEDoc.objects.filter(stock_item__stock_code=stock_code,invalid=0)
        
        for stock_item_edoc in stock_item_edocs:
            key = stock_item_edoc.stock_code+stock_item_edoc.doc_name+stock_item_edoc.version        
            parts_edoc = parts_edocs_dict.get(key,False)
            if parts_edoc is False:
                return True
        
        return False

    parts = Parts.objects.filter(stock_code=stock_item.stock_code)
    total_parts = parts.count()
    not_complete = 0
    for part in parts:
        is_outstanding = check_parts_edoc(part)
        if is_outstanding:
            not_complete += 1
    
    complete = total_parts-not_complete
    if complete>0:
        return round(complete/total_parts,2)
    return 1

def sw_version_completion_rate(stock_item):
    def check_parts_version(part:Parts):
        versions = VersionMaster.objects.filter(stock_code=part.stock_code, deleted=False)
        sw_vers = [v.sw_ver.replace(" ", "") for v in versions ]
        part_sws = part.sw_ver.split(',')

        for part_sw in part_sws:
            if part_sw not in sw_vers:
                return True
            
        return False

    parts = Parts.objects.filter(stock_code=stock_item.stock_code)
    total_parts = parts.count()
    not_complete = 0
    for part in parts:
        is_outstanding = check_parts_version(part)
        if is_outstanding:
            not_complete += 1

        complete = total_parts-not_complete
        if complete>0:
            return round(complete/total_parts,2)
        return 1


def get_extra_stock_master_list_edoc_completion_rate():
       
    stock_dict = {
        s.id: edoc_completion_rate(s)
        for s in  StockItem.objects.all()
    }

    return stock_dict

def get_extra_stock_master_list_sw_ver_completion_rate():
    stock_dict = {
        s.id: sw_version_completion_rate(s)
        for s in  StockItem.objects.all()
    }

    return stock_dict